/* * Copyright 2017 OmniFaces * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ package org.omnifaces.taghandler; import static java.lang.String.format; import static org.omnifaces.util.FacesLocal.getFaceletContext; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.io.IOException; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import java.util.Map.Entry; import javax.el.ELContext; import javax.el.ValueExpression; import javax.faces.FacesException; import javax.faces.application.Application; import javax.faces.component.UIComponent; import javax.faces.context.FacesContext; import javax.faces.view.AttachedObjectHandler; import javax.faces.view.facelets.ConverterHandler; import javax.faces.view.facelets.DelegatingMetaTagHandler; import javax.faces.view.facelets.FaceletContext; import javax.faces.view.facelets.MetaRuleset; import javax.faces.view.facelets.TagAttribute; import javax.faces.view.facelets.TagHandlerDelegate; import javax.faces.view.facelets.ValidatorHandler; /** * Helper class for OmniFaces {@link Converter} and {@link Validator}. It can't be an abstract class as they have to * extend from {@link ConverterHandler} and {@link ValidatorHandler}. * * @author Bauke Scholtz */ final class DeferredTagHandlerHelper { // Private constants ---------------------------------------------------------------------------------------------- private static final String ERROR_MISSING_ID = "%s '%s' or 'binding' attribute must be specified."; private static final String ERROR_INVALID_ID = "%s '%s' attribute must refer an valid %1$s ID. The %1$s ID '%s' cannot be found."; // Constructors --------------------------------------------------------------------------------------------------- /** * Hide constructor. */ private DeferredTagHandlerHelper() { // } // Actions -------------------------------------------------------------------------------------------------------- /** * Create the tag instance based on the <code>binding</code> and/or <code>instanceId</code> attribute. * @param context The involved facelet context. * @param tag The involved tag handler. * @param instanceId The attribute name representing the instance ID. * @return The created instance. * @throws IllegalArgumentException If the <code>validatorId</code> attribute is invalid or missing while the * <code>binding</code> attribute is also missing. * @throws ClassCastException When <code>T</code> is of wrong type. */ @SuppressWarnings("unchecked") static <T> T createInstance(FaceletContext context, DeferredTagHandler tag, String instanceId) { ValueExpression binding = getValueExpression(context, tag, "binding", Object.class); ValueExpression id = getValueExpression(context, tag, instanceId, String.class); T instance = null; if (binding != null) { instance = (T) binding.getValue(context); } if (id != null) { try { instance = tag.create(context.getFacesContext().getApplication(), (String) id.getValue(context)); } catch (FacesException e) { throw new IllegalArgumentException( format(ERROR_INVALID_ID, tag.getClass().getSimpleName(), instanceId, id), e); } if (binding != null) { binding.setValue(context, instance); } } else if (instance == null) { throw new IllegalArgumentException( format(ERROR_MISSING_ID, tag.getClass().getSimpleName(), instanceId)); } return instance; } /** * Collect the deferred attributes of the given object. If the property is a literal text (i.e. no EL expression), * then it will just be set directly on the given object, else it will be collected as {@link ValueExpression} and * setter method pairs and returned. * @param context The involved facelet context. * @param instance The instance to collect EL properties for. * @return The deferred attributes of the given object. */ static <T> DeferredAttributes collectDeferredAttributes (FaceletContext context, DeferredTagHandler tag, T instance) { DeferredAttributes attributes = new DeferredAttributes(); try { for (PropertyDescriptor property : Introspector.getBeanInfo(instance.getClass()).getPropertyDescriptors()) { Method setter = property.getWriteMethod(); ValueExpression valueExpression = getValueExpression(context, tag, property.getName(), property.getPropertyType()); if (setter == null || valueExpression == null) { continue; } if (valueExpression.isLiteralText()) { setter.invoke(instance, valueExpression.getValue(context)); } else { attributes.add(setter, valueExpression); } } } catch (Exception e) { throw new FacesException(e); } return attributes; } /** * Convenience method to get the given attribute as a {@link ValueExpression}, or <code>null</code> if there is * no such attribute. * @param context The involved facelet context. * @param name The attribute name to return the value expression for. * @param type The type of the value expression. * @return The given attribute as a {@link ValueExpression}. */ static <T> ValueExpression getValueExpression (FaceletContext context, DeferredTagHandler tag, String name, Class<T> type) { TagAttribute attribute = tag.getTagAttribute(name); return (attribute != null) ? attribute.getValueExpression(context, type) : null; } // Nested classes ------------------------------------------------------------------------------------------------- /** * So that we can extract tag attributes from both {@link ConverterHandler} and {@link ValidatorHandler} and create * concrete {@link Converter} and {@link Validator} instances. * * @author Bauke Scholtz */ interface DeferredTagHandler { /** * Just return TagHandler#getAttribute() via a public method (it's by default protected and otherwise thus * unavailable inside collectDeferredAttributes(). * @param name The attribute name. * @return The tag attribute associated with given attribute name. */ TagAttribute getTagAttribute(String name); /** * Create the concrete {@link Converter} or {@link Validator}. * @param <T> The expected return type. * @param application The involved faces application. * @param id The converter or validator ID. * @return The concrete {@link Converter} or {@link Validator}. */ <T> T create(Application application, String id); } /** * Convenience class which holds all deferred attribute setters and value expressions. * * @author Bauke Scholtz */ static final class DeferredAttributes { private Map<Method, ValueExpression> attributes; private DeferredAttributes() { attributes = new HashMap<>(); } private void add(Method setter, ValueExpression valueExpression) { attributes.put(setter, valueExpression); } public void invokeSetters(ELContext elContext, Object object) { for (Entry<Method, ValueExpression> entry : attributes.entrySet()) { try { entry.getKey().invoke(object, entry.getValue().getValue(elContext)); } catch (Exception e) { throw new FacesException(e); } } } } /** * Convenience tag handler delegate which delegates {@link #applyAttachedObject(FacesContext, UIComponent)} to the * owning tag itself. This all is used when composite components come into picture. * * @author Bauke Scholtz */ static class DeferredTagHandlerDelegate extends TagHandlerDelegate implements AttachedObjectHandler { private DelegatingMetaTagHandler tag; private TagHandlerDelegate delegate; public DeferredTagHandlerDelegate(DelegatingMetaTagHandler tag, TagHandlerDelegate delegate) { this.tag = tag; this.delegate = delegate; } @Override public void apply(FaceletContext context, UIComponent component) throws IOException { delegate.apply(context, component); } @Override public String getFor() { return ((AttachedObjectHandler) delegate).getFor(); } @Override public void applyAttachedObject(FacesContext context, UIComponent parent) { try { tag.apply(getFaceletContext(context), parent); } catch (IOException e) { throw new FacesException(e); } } @Override public MetaRuleset createMetaRuleset(@SuppressWarnings("rawtypes") Class type) { return delegate.createMetaRuleset(type); } } }